/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./Engine3D.h"
#include "./Math/Math.h"
#include "../Macros/Macros.h"
#include "./Input/EllipsoidManager.h"
#include "./Input/Cursor3DManager.h"
#include "./Input/GregoryManager.h"
#include "./Objects/PointsSet.h"
#include "./Objects/Surfaces/BezierSurface.h"
#include "./Objects/Surfaces/BSplineSurface.h"
#include "./Objects/Surfaces/GregorySurface.h"
#include "./Objects/Surfaces/TriGregory.h"
#include "./Objects/Curves/BSplineCurve.h"

#include "../MemTracer/MemoryTracker.h"

/**************************************************************
* Constructors                                                *
***************************************************************/
Engine3D::Engine3D(): mDisplayHelp(true), mStereoView(false) {
	Externals::mConstants = new MathConstants();
	mInputManager = new InputManager();	
	mProjectionPlane = new ProjectionPlane();
	mRenderer = new Renderer();
	mScene = new Scene3D();
	mLeftEyeColor = Color::ToWinColor(RED);
	mRightEyeColor = Color::ToWinColor(BLUE);
}

Engine3D::~Engine3D() {
	if (mProjectionPlane) {
		delete mProjectionPlane;
	}
	if (mRenderer) {
		delete mRenderer;
	}
	if (Externals::mConstants) {
		delete Externals::mConstants;
		Externals::mConstants = cNull;
	}
	if (mInputManager) {
		delete mInputManager;
	}
	if (mScene) {
		delete mScene;
	}
}

/**************************************************************
* Other helpful functions                                     *
***************************************************************/
Renderer& Engine3D::GetRenderer() {
	return *mRenderer;
}

cVoid Engine3D::Init(HWND windowHandle) {
	mRenderer->Init(windowHandle);
	mProjectionPlane->SetViewport(mRenderer->GetWidth(), mRenderer->GetHeight());
}

cVoid Engine3D::Render(HDC hdc, cRect rect) {
	Matrix4D matrix, matrixLeftEye, matrixRightEye;

	if (!mStereoView) {
		matrix = Matrix4D::Perspective(mProjectionPlane->GetFOV());
		mScene->Render(*mRenderer, matrix, *mProjectionPlane);
		if (mInputManager) {
			Cursor3DManager* cursorManager = dynamic_cast<Cursor3DManager*>(mInputManager);
			if (cursorManager) {
				mScene->RenderCursor(*mRenderer, matrix, *mProjectionPlane);
			}
		}
	} else {
		matrixLeftEye = Matrix4D::PerspectiveLeftEye(mProjectionPlane->GetFOV(),
			mProjectionPlane->GetEyeDistance());
		matrixRightEye = Matrix4D::PerspectiveRightEye(mProjectionPlane->GetFOV(),
			mProjectionPlane->GetEyeDistance());
		mScene->Render(*mRenderer, matrixLeftEye, *mProjectionPlane, mLeftEyeColor);
		mScene->Render(*mRenderer, matrixRightEye, *mProjectionPlane, mRightEyeColor);
		if (mInputManager) {
			Cursor3DManager* cursorManager = dynamic_cast<Cursor3DManager*>(mInputManager);
			if (cursorManager) {
				mScene->RenderCursor(*mRenderer, matrixLeftEye, *mProjectionPlane, mLeftEyeColor);
				mScene->RenderCursor(*mRenderer, matrixRightEye, *mProjectionPlane, mRightEyeColor);
			}
		}
	}
	if (mDisplayHelp) {
		DisplayHelp(matrix = Matrix4D::Perspective(mProjectionPlane->GetFOV()));
	}
	mRenderer->Render(hdc, rect);
}

cVoid Engine3D::Refresh() {
	mFPSCounter.CalculateFPS();
	//mRenderer->Clear();
	mRenderer->Refresh();
}

cVoid Engine3D::Clear() {
	mRenderer->Clear();
}

cVoid Engine3D::Resize() {
	mRenderer->Resize();
}

cVoid Engine3D::SetInputManager(InputManager* manager) {
	if (manager) {
		if (mInputManager) {
			delete mInputManager;
			mInputManager = cNull;
		}
		if (dynamic_cast<EllipsoidManager*>(manager)) {
			mStereoView = false;
		}
		mInputManager = manager;
	}
}

InputManager* Engine3D::GetInputManager() {
	return mInputManager;
}

cVoid Engine3D::ResetInputManager() {
	if (mInputManager) {
		delete mInputManager;
		mInputManager = new InputManager();
	}
}

cInt Engine3D::GetWidth() {
	return mRenderer->GetWidth();
}

cInt Engine3D::GetHeight() {
	return mRenderer->GetHeight();
}

cVoid Engine3D::ChangeFOV(cFloat fDistance) {
	mProjectionPlane->ChangeFov(fDistance);
}

cFloat Engine3D::GetFOV() {
	return mProjectionPlane->GetFOV();
}

cBool Engine3D::GetDisplayHelp() {
	return mDisplayHelp;
}

cVoid Engine3D::SwitchDisplayHelp() {
	mDisplayHelp = !mDisplayHelp;
}

cVoid Engine3D::SwitchStereoMonoView() {
	mStereoView = !mStereoView;
}

cVoid Engine3D::SetLeftEyeColor(cWinColor color) {
	mLeftEyeColor = color;
}

cVoid Engine3D::SetRightEyeColor(cWinColor color) {
	mRightEyeColor = color;
}

cVoid Engine3D::ChangeEyeDistance(cFloat fEyeDistance) {
	mProjectionPlane->ChangeEyeDistance(fEyeDistance);
}

Matrix4D& Engine3D::GetWorldMatrix(){
	return mScene->GetWorldMatrix();
}

cVoid Engine3D::DisplayHelp(Matrix4D& matrix) {
	cChar pcBuff [MAX_BUF_SIZE];
	cInt iLineNr = 3;
	cInt iHeight = mRenderer->GetHeight();
	cBool bRendered = false;
	
	sprintf_s(pcBuff, "%4.0f", mFPSCounter.GetFPS());
	mRenderer->DrawString(mRenderer->GetWidth() - 7 * H_TXT_SPACER, 0, pcBuff, 
		false, false, true, RGB(255, 255, 0));
	mRenderer->DrawString(mRenderer->GetWidth() - H_TXT_SPACER, 0, " : FPS", 
		true, false, true, RGB(255, 255, 0));

	sprintf_s(pcBuff, "[Q/A] - X-axis rotations, [W/S] - Y-axis rotations, [E/D] - Z-axis rotations, [Z/X] - Z Translation");
	mRenderer->DrawString(H_TXT_SPACER, iHeight - iLineNr-- * V_TXT_SPACER, pcBuff, 
		false, true, true);

	sprintf_s(pcBuff, "[LMB&Drag] - XY Translation, [RMB&Drag] - Scaling");
	mRenderer->DrawString(H_TXT_SPACER, iHeight - iLineNr-- * V_TXT_SPACER, pcBuff, 
		false, true, true);

	sprintf_s(pcBuff, "[C/V] - Increase/Decrease Perspective, [Ctrl/Alt + E] - Increase/Decrease Eyes Distance");
	mRenderer->DrawString(H_TXT_SPACER, iHeight - iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true);

	if (mInputManager) {
		Object3D* obj = mInputManager->GetObject();
		if (obj) {
			if (dynamic_cast<GregoryManager*>(mInputManager)) {
				GregoryManager* manager = dynamic_cast<GregoryManager*>(mInputManager);
				if (manager->GetObject() != cNull) {
					TriGregory* gregorySurface = (TriGregory*) manager->GetObject();
					gregorySurface->DisplayInfo(*mRenderer);
					bRendered = true;
				} else {
					manager->DisplayInfo(*mRenderer);
				}
			} else if (dynamic_cast<BezierSurface*>(obj)) {
				BezierSurface* bezierSurface = dynamic_cast<BezierSurface*>(obj);
				if (bezierSurface->ArePointsCoordinatesVisible()) {
					((Mesh*) bezierSurface)->DisplayInfo(*mRenderer, matrix * mScene->GetWorldMatrix(), *mProjectionPlane);
				}
				bezierSurface->DisplayInfo(*mRenderer);
				bRendered = true;
			} else if (dynamic_cast<BSplineSurface*>(obj)) {
				BSplineSurface* bSplineSurface = dynamic_cast<BSplineSurface*>(obj);
				if (bSplineSurface->ArePointsCoordinatesVisible()) {
					((Mesh*) bSplineSurface)->DisplayInfo(*mRenderer, matrix * mScene->GetWorldMatrix(), *mProjectionPlane);
				}
				bSplineSurface->DisplayInfo(*mRenderer);
				bRendered = true;
			} else if (dynamic_cast<BSplineCurve*>(obj)) {
				BSplineCurve* bSpline = dynamic_cast<BSplineCurve*>(obj);
				if (bSpline->ArePointsCordsVisible()) {
					bSpline->DisplayInfo(*mRenderer, matrix * mScene->GetWorldMatrix(), *mProjectionPlane);
				}
				bSpline->DisplayInfo(*mRenderer);
				bRendered = true;
			} else if (dynamic_cast<PointsSet*>(obj)) {
				PointsSet* pointsSet = dynamic_cast<PointsSet*>(obj);
				if (!bRendered) {
					if (pointsSet->ArePointsCordsVisible()) {
						pointsSet->DisplayInfo(*mRenderer, matrix * mScene->GetWorldMatrix(), *mProjectionPlane);
					}
					pointsSet->DisplayInfo(*mRenderer);
				}
			} else {
				obj->DisplayInfo(*mRenderer);
			}
		} else {
			if (dynamic_cast<GregoryManager*>(mInputManager)) {
				GregoryManager* manager = dynamic_cast<GregoryManager*>(mInputManager);
				manager->DisplayInfo(*mRenderer);
			}
		}
	}
}